use crate::config::app_error::AppResult;
use crate::extension::index::index::indexer::DefaultIndexer;
use crate::extension::index::index::query::query::all::All;
use crate::extension::index::index::query::query::and::And;
use crate::extension::index::index::query::query::between::Between;
use crate::extension::index::index::query::query::equal::Equal;
use crate::extension::index::index::query::query::greater_than::GreaterThan;
use crate::extension::index::index::query::query::in_query::In;
use crate::extension::index::index::query::query::is_not_null::IsNull;
use crate::extension::index::index::query::query::is_null::IsNotNull;
use crate::extension::index::index::query::query::less_than::LessThan;
use crate::extension::index::index::query::query::not::Not;
use crate::extension::index::index::query::query::not_equal::NotEqual;
use crate::extension::index::index::query::query::or::Or;
use crate::extension::index::index::query::query::string_contains::StringContains;
use crate::extension::index::index::query::query::string_ends_with::StringEndsWith;
use crate::extension::index::index::query::query::string_starts_with::StringStartsWith;
use crate::extension::index::index::query::query::{BaseQuery, QueryIndexView};
use halo_model::{ExtensionOperator, GVK};
use ordermap::OrderSet;
use std::fmt::{Display, Formatter};
use std::hash::Hash;

pub enum Query {
    All(All),
    And(And),
    Or(Or),
    Between(Between),
    GreaterThan(GreaterThan),
    In(In),
    IsNull(IsNull),
    IsNotNull(IsNotNull),
    LessThan(LessThan),
    Equal(Equal),
    Not(Not),
    NotEqual(NotEqual),
    StringStartsWith(StringStartsWith),
    StringEndsWith(StringEndsWith),
    StringContains(StringContains),
}
impl BaseQuery for Query {
    fn matches(
        &self,
        index_view: &QueryIndexView<DefaultIndexer>,
    ) -> AppResult<OrderSet<String>> {
        match self {
            Query::All(all) => all.matches(index_view),
            Query::And(x) => x.matches(index_view),
            Query::Or(x) => x.matches(index_view),
            Query::Between(x) => x.matches(index_view),
            Query::GreaterThan(x) => x.matches(index_view),
            Query::In(x) => x.matches(index_view),
            Query::IsNull(x) => x.matches(index_view),
            Query::IsNotNull(x) => x.matches(index_view),
            Query::LessThan(x) => x.matches(index_view),
            Query::Equal(x) => x.matches(index_view),
            Query::Not(x) => x.matches(index_view),
            Query::NotEqual(x) => x.matches(index_view),
            Query::StringStartsWith(x) => x.matches(index_view),
            Query::StringEndsWith(x) => x.matches(index_view),
            Query::StringContains(x) => x.matches(index_view),
        }
    }
}

impl Display for Query {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Query::All(x) => write!(f, "All {} ", &x.field_name),
            Query::And(x) => {
                for x in &x.child_queries {
                    write!(f, "{} ", x);
                }
                write!(f, "And ")
            }
            Query::Or(x) => write!(f, "Or  "),
            Query::Between(x) => write!(f, "Between {} ", &x.field_name),
            Query::GreaterThan(x) => write!(f, "GreaterThan {} ", &x.field_name),
            Query::In(x) => write!(f, "In {} ", &x.field_name),
            Query::IsNull(x) => write!(f, "IsNull {} ", &x.field_name),
            Query::IsNotNull(x) => write!(f, "IsNotNull {} ", &x.field_name),
            Query::LessThan(x) => write!(f, "LessThan {} ", &x.field_name),
            Query::Equal(x) => write!(f, "Equal {} ", &x.field_name),
            Query::Not(x) => write!(f, "Not "),
            Query::NotEqual(x) => write!(f, "NotEqual {} ", &x.field_name),
            Query::StringStartsWith(x) => write!(f, "StringStartsWith {} ", &x.field_name),
            Query::StringEndsWith(x) => write!(f, "StringEndsWith {} ", &x.field_name),
            Query::StringContains(x) => write!(f, "StringContains {} ", &x.field_name),
        }
    }
}

pub fn all() -> Query {
    Query::All(All::new("metadata.name".to_string()))
}

pub fn all_with_field(field_name: String) -> All {
    All::new(field_name)
}
pub fn is_null(field_name: String) -> IsNull {
    IsNull::new(field_name)
}
pub fn is_not_null(field_name: String) -> IsNotNull {
    IsNotNull::new(field_name)
}
pub fn not_equal(field_name: String, attribute_value: String) -> NotEqual {
    NotEqual::new(field_name, attribute_value)
}

pub fn not_equal_other_field(field_name: String, other_field_name: String) -> NotEqual {
    NotEqual::new_ref(field_name, other_field_name, true)
}
pub fn equal(field_name: &str, attribute_value: String) -> Equal {
    Equal::new(field_name.to_string(), attribute_value)
}

pub fn equal_other_field(field_name: String, other_field_name: String) -> Equal {
    Equal::new_all(field_name, other_field_name, true)
}

pub fn less_than_other_field(field_name: String, other_field_name: String) -> LessThan {
    LessThan::new_all(field_name, other_field_name, false, true)
}
pub fn less_than_or_equal_other_field(field_name: String, other_field_name: String) -> LessThan {
    LessThan::new_all(field_name, other_field_name, true, true)
}
pub fn less_than(field_name: String, attribute_value: String) -> LessThan {
    LessThan::new(field_name, attribute_value, false)
}

pub fn less_than_or_equal(field_name: String, attribute_value: String) -> LessThan {
    LessThan::new(field_name, attribute_value, true)
}
pub fn greater_than(field_name: String, attribute_value: String) -> LessThan {
    LessThan::new(field_name, attribute_value, false)
}
pub fn greater_than_or_equal(field_name: String, attribute_value: String) -> LessThan {
    LessThan::new(field_name, attribute_value, true)
}
pub fn greater_than_other_field(field_name: String, other_field_name: String) -> LessThan {
    LessThan::new_all(field_name, other_field_name, false, true)
}
pub fn greater_than_or_equal_other_field(field_name: String, other_field_name: String) -> LessThan {
    LessThan::new_all(field_name, other_field_name, true, true)
}
pub fn in_query(field_name: String, values: OrderSet<String>) -> In {
    In::new(field_name, values)
}
pub fn and(query1: Query, query2: Query) -> AppResult<Query> {
    Ok(Query::And(And::new(vec![query1, query2])?))
}

pub fn and_vec(queries: Vec<Query>) -> AppResult<Query> {
    Ok(Query::And(And::new(queries)?))
}

pub fn or(query1: Query, query2: Query) -> Or {
    Or::new(vec![query1, query2])
}

pub fn contains(field_name: &str, value: String) -> StringContains {
    StringContains::new(field_name.to_string(), value)
}
